<?php

/**
 * SimplifyPHP Framework
 *
 * This file is part of SimplifyPHP Framework.
 *
 * SimplifyPHP Framework is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * SimplifyPHP Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author Rodrigo Rutkoski Rodrigues, <rutkoski@gmail.com>
 */

require_once ('api/IApplicationController.php');

/**
 * Application Controller
 *
 * @author Rodrigo Rutkoski Rodrigues <rutkoski@gmail.com>
 * @package Simplify_Kernel_Core
 */
class ApplicationControllerImpl implements IApplicationController
{

  /**
   *
   * @var Config
   */
  protected $config;

  /**
   *
   * @var IRequest
   */
  protected $request;

  /**
   *
   * @var IResponse
   */
  protected $response;

  /**
   *
   * @var IRouter
   */
  protected $router;

  /**
   *
   * @var string
   */
  protected $route;

  /**
   *
   * @var array
   */
  protected static $resp = array();

  /**
   *
   * @var array
   */
  protected $components = array();

  /**
   *
   * @param $config
   * @return unknown_type
   */
  public function __construct(Config $config)
  {
    $this->config = $config;
  }

  /**
   * Automagically call application components
   * 
   * @return object
   */
  public function __call($name, $arguments)
  {
    if (strpos($name, 'get') === 0) {
      $name = strtolower(substr($name, 3));
      return $this->getComponentInstance($name);
    }
  }

  /**
   * Automagically call application components
   * 
   * @return object
   */
  public function __get($name)
  {
    return $this->getComponentInstance($name);
  }

  /**
   * Register application components
   * 
   * @param string $name component name
   * @param mixed $instance component instance or class
   * @return void
   */
  public function registerComponent($name, $instance)
  {
    $this->components[$name] = $instance;
  }

  /**
   * Get application component
   * 
   * @return object
   */
  public function getComponentInstance($name)
  {
    if (! isset($this->components[$name])) {
      throw new Exception("Component not registered in application: <b>$name</b>");
    }

    if (is_string($this->components[$name])) {
      $class = $this->components[$name];
      $this->components[$name] = new $class();
    }

    return $this->components[$name];
  }

  /**
   * Callback. Called before user config file has been loaded.
   * 
   * @return void
   */
  public function config()
  {
    foreach ($this->components as $name => $component) {
      if (method_exists($component, 'config')) {
        $this->getComponentInstance($name)->config();
      }
    }
  }

  /**
   * Callback. Called after config has been done.
   *
   * @return void
   */
  public function initialize()
  {
    foreach ($this->components as $name => $component) {
      if (method_exists($component, 'initialize')) {
        $this->getComponentInstance($name)->initialize();
      }
    }
  }

  /**
   *
   * @return Config
   */
  public function getConfig()
  {
    return $this->config;
  }

  public function setConfig(Config $config)
  {
    $this->config = $config;
  }

  /**
   *
   * @return IRouter
   */
  public function getRouter()
  {
    return $this->router;
  }

  public function setRouter(IRouter $router)
  {
    $this->router = $router;
    $this->registerComponent('router', $router);
  }

  /**
   *
   * @return string
   */
  public function getRoute()
  {
    return $this->route;
  }

  /**
   *
   * @return IRequest
   */
  public function getRequest()
  {
    return $this->request;
  }

  public function setRequest(IRequest $request)
  {
    $this->request = $request;
    $this->registerComponent('request', $request);
  }

  /**
   *
   * @return IResponse
   */
  public function getResponse()
  {
    return $this->response;
  }

  public function setResponse(IResponse $response)
  {
    $this->response = $response;
    $this->registerComponent('response', $response);
  }

  /**
   * (non-PHPdoc)
   * @see simplify/kernel/api/IApplicationController#dispatch()
   */
  public function dispatch()
  {
    $routes = array(
      //'display' => array('/*', array('controller' => 'home', 'action' => 'display')),
      'gateway' => array('/gateway', array('controller' => 'home', 'action' => 'gateway')),
      'controller' => array('/:controller', array('action' => 'index')),
      'controller_action' => array('/:controller/:action'),
      'default' => array('/:controller/:action', array('action' => 'index')),
      'home' => array('/', array('controller' => 'home', 'action' => 'index')),
      'page_not_found' => array('/404', array('controller' => 'home', 'action' => '404')),
    );

    foreach ($routes as $id => $route) {
      Simplify::getRouter()->connect($id, sy_get_param($route, 0), sy_get_param($route, 1), sy_get_param($route, 2));
    }

    try {
      $output = $this->forward($this->request->route());
    }
    catch (RouteNotFoundException $e) {
      $output = $this->forward('page_not_found');
    }

    return $output;
  }

  /**
   * (non-PHPdoc)
   * @see simplify/kernel/core/IApplicationController#forward($route)
   */
  public function forward($route)
  {
    try {
      if (strpos($route, '/') === false) {
        $args = func_get_args();
        $params = (array) sy_get_param($args, 1);
        $route = $this->router->build($route, $params);
      }

      $this->route = $route;

      Simplify::callAction('before_forward');

      $data = $this->router->parse($route);

      $this->createResponse();

      $action = $data->get('action');
      $params = $data->get('params');

      $Controller = ActionControllerImpl::factory($data);
      $Controller->setRoute($route);
      $Controller->initialize();

      $output = $Controller->callActionArray($action, $params);

      $Controller->finalize();

      if ($output === null) {
        $this->response->setLayout($Controller->getLayout());
        $this->response->setTemplate($Controller->getTemplate());
        $output = $this->response->render(false);
      }
      elseif (is_a($output, 'IView')) {
        $output->setLayout($Controller->getLayout());
        $output = $output->render();
      }

      $output = $this->response->output($output);

      $this->restoreResponse();
    }
    catch (Exception $e) {
      $output = Simplify::handleException($e);
    }

    return $output;
  }

  public function redirect($route, $get = array(), $replace_headers = false, $http_response_code = null)
  {
    if (is_array($route)) {
      $route = $this->router->build(sy_get_param($route, 0), sy_get_param($route, 1));
    }

    $url = $route;

    if (! sy_url_is_absolute($url)) {
      while (strpos($route, '//') === 0) {
        $route = str_replace('//', '/', $route);
      }

      while (strpos($route, '/') === strlen($route) - 1) {
        $route = substr($route, 0, strlen($route) - 2);
      }

      if (strpos($route, '/') !== 0) {
        $route = '/' . $route;
      }

      $url = Simplify::getConfig()->get('app_url');

      if (! Simplify::getRequest()->pretty()) {
        $url .= '/' . Simplify::getRequest()->self();
      }

      $url .= $route;

      if (! empty($get)) {
       $url .= '?' . http_build_query($get);
      }
    }

    header("Location: $url", $replace_headers, $http_response_code);

    return false;
  }

  protected function createResponse()
  {
    if ($this->response) {
      self::$resp[] = $this->response;
    }

    $resp = new AppResponse();

    $this->setResponse($resp);

    return $this->getResponse();
  }

  protected function restoreResponse()
  {
    $old = self::$resp;

    $resp = array_pop(self::$resp);

    if ($resp) {
      $this->setResponse($resp);
    }

    return $old;
  }

}

?>